﻿using System;
using SubSonic;
using Medianamik.Core.DAL.SubSonicDAL;
using System.Linq;
using System.Collections.Generic;
using SubSonicNode = Medianamik.Core.DAL.SubSonicDAL.Node;
using Medianamik.Core.DAL;
using System.Globalization;
using Medianamik.Core.Sugar;

namespace Medianamik.Core
{
    public static class NodePropertyValueConverter
    {
        public static void ConvertAllNodePropertyValueToNodeRevisions()
        {
            var conversionDate = DateTime.Now;
            var query = new Select().From(SubSonicNode.Schema);

            var allNodesCount = query.GetRecordCount();
            var nodeIDs = new HashSet<Guid>();
            var doubles = new HashSet<Guid>();
            var unresolvedNodeIDs = new HashSet<Guid>();
            const int bacthSize = 100;

            var j = 1;
            for (var i = 0; i < allNodesCount; i += bacthSize)
            {
                var batchNodeIDs = query.Paged(j, bacthSize)
                    .ExecuteAsCollection<NodeCollection>().ToList().Select(n => n.NodeId);
                var revisions = new List<NodeRevision>();

                foreach (var nodeID in batchNodeIDs)
                {
                    if (nodeIDs.Add(nodeID))
                    {
                        var node = NodeManager.GetNode(nodeID);

                        if (node == null)
                        {
                            unresolvedNodeIDs.Add(nodeID);
                        }
                        else
                        {
                            if (node.IsVersioned)
                            {
                                var publications = new HashSet<Publication>();

                                if (node.IsLanguageNeutral)
                                {
                                    node.Instances[""].Publications.ForEach(p => publications.Add(p));
                                }
                                else
                                {
                                    node.Instances.SelectMany(inst => inst.Publications).ForEach(
                                        p => publications.Add(p));
                                }

                                revisions.AddRange(SerialyzeAndSaveNodePropertiesByRevision(node, publications, conversionDate));
                            }

                            //TODO: Ajouter la révision en cours (null)
                            revisions.AddRange(GetNodeRevisions(node, null, conversionDate));
                        }
                    }
                    else
                    {
                        doubles.Add(nodeID);
                    }
                }

                SaveRevisions(revisions);
                j++;
            }
        }

        private static void SaveRevisions(List<NodeRevision> revisions)
        {
            var transaction = new Transaction(NodeRevision.Schema.Provider);
            transaction.AddRange(revisions.Select(r => r.GetSaveCommand("sa")).NotNull());

            if (transaction.HasCommands)
                transaction.Execute();
        }

        private static IList<NodeRevision> SerialyzeAndSaveNodePropertiesByRevision(Node node,
            HashSet<Publication> publications, DateTime conversionDate)
        {
            var revisions = new List<NodeRevision>();

            foreach (var publication in publications)
            {
                revisions.AddRange(GetNodeRevisions(node, publication, conversionDate));
            }

            return revisions;
        }

        private static IEnumerable<NodeRevision> GetNodeRevisions(Node node, Publication publication, DateTime conversionDate)
        {
            var properties = new List<NodeProperty>();
            var revision = publication == null ? default(int?) : publication.Revision;

            if (node.IsLanguageNeutral)
            {
                if (!node.Instances.Exists(""))
                    node.AddNodeInstance("");

                properties.AddRange(GetPropertiesForRevision(node.Instances[""], revision));
            }
            else
            {
                properties.AddRange(node.Instances.SelectMany(i =>
                    GetPropertiesForRevision(i, revision)));
            }

            //HashSet important pour filtrer les props neutre a la lanfue (fois le nombre d'instance)
            var liteProperties = new HashSet<NodePropertyValueLite>();

            foreach (var prop in properties.Where(prop => prop as ManyToManyNodeProperty == null))
            {
                liteProperties.Add(new NodePropertyValueLite(prop));
            }

            var nodeRevisions = new List<NodeRevision>();

            liteProperties.GroupBy(p => p.CultureName).ForEach(propertiesByCulture =>
               {
                   var nodeRevision = new NodeRevision();
                   nodeRevision.NodeId = node.ID;
                   nodeRevision.CreatedOn = conversionDate;
                   nodeRevision.PublishedOn = publication == null ? default(DateTime?) : publication.StartDate;
                   nodeRevision.Data = propertiesByCulture.SerializeProperties(propertiesByCulture.Key);
                   nodeRevision.CultureName = propertiesByCulture.Key;
                   nodeRevisions.Add(nodeRevision);
               });

            return nodeRevisions;
        }


        public static IEnumerable<NodeProperty> GetPropertiesForRevision(NodeInstance nodeInstance,
            int? pRevision)
        {
            return new NodePropertyCollection(nodeInstance,
                DALProviderManager.DALProvider.NodeDALProvider
                .OldGetNodePropertyValues(nodeInstance.Node, pRevision)
                .Where(p =>
                    p.Culture.Name.EqualsIgnoreCase(nodeInstance.Culture.Name) ||
                    p.Culture.Name.EqualsIgnoreCase(CultureInfo.InvariantCulture.Name)));
        }

        public static void ConvertNodeRevisionToNewNodeRevision()
        {
            ConvertNodeRevisions(false);
        }

        public static void ConvertNodeRevisionAllInOneToNodeRevisionWithCulture()
        {
            ConvertNodeRevisions(true);
        }

        private static void ConvertNodeRevisions(bool newVersion)
        {
            var query = new Select().From(NodeRevision.Schema);
            query.Where(NodeRevision.Columns.CultureName).IsNull();
            var revisionsCount = query.GetRecordCount();
            const int bacthSize = 100;
            var allNodeRevisions = new List<NodeRevision>();
            var numberOfBatches = (decimal)((decimal)revisionsCount / (decimal)bacthSize);
            numberOfBatches = Math.Ceiling(numberOfBatches);

            for (var j = 1; j <= numberOfBatches; j++)
            {
                allNodeRevisions.AddRange(query.Paged(j, bacthSize).ExecuteAsCollection<NodeRevisionCollection>());
            }

            var revisions = new List<NodeRevision>();

            foreach (var nodeRevision in allNodeRevisions)
            {
                var firstRevision = true;
                var propertyValueLites = newVersion ? nodeRevision.Data.DeserializeProperties(nodeRevision.NodeId) :
                    nodeRevision.Data.Deserialize<List<NodePropertyValueLite>>();

                // Faire une révision pour chaque langue
                var culturePropertyListDictionary = new Dictionary<string, List<NodePropertyValueLite>>();

                propertyValueLites.ForEach(p =>
                    {
                        if (!culturePropertyListDictionary.ContainsKey(p.CultureName))
                        {
                            culturePropertyListDictionary.Add(p.CultureName, new List<NodePropertyValueLite>());
                        }
                        culturePropertyListDictionary[p.CultureName].Add(p);
                    });

                foreach (var culturePropertyList in culturePropertyListDictionary)
                {
                    NodeRevision nodeRevisionToUse;

                    if (firstRevision)
                    {
                        firstRevision = false;
                        nodeRevisionToUse = nodeRevision;
                    }
                    else
                    {
                        nodeRevisionToUse = new NodeRevision();
                        nodeRevisionToUse.CreatedBy = nodeRevision.CreatedBy;
                        nodeRevisionToUse.CreatedOn = nodeRevision.CreatedOn;
                        nodeRevisionToUse.NodeId = nodeRevision.NodeId;
                        nodeRevisionToUse.PublishedOn = nodeRevision.PublishedOn;
                        nodeRevisionToUse.NodeRevisionId = Guid.NewGuid();
                    }

                    nodeRevisionToUse.CultureName = culturePropertyList.Key;
                    nodeRevisionToUse.Data = propertyValueLites.SerializeProperties(culturePropertyList.Key);
                    revisions.Add(nodeRevisionToUse);
                }

                if (revisions.Count == 100)
                {
                    SaveRevisions(revisions);
                    revisions.Clear();
                }
            }

            SaveRevisions(revisions);

            var deleteQuery = new Query(NodeRevision.Schema);
            deleteQuery.QueryType = QueryType.Delete;
            deleteQuery.WHERE(NodeRevision.Columns.CultureName, Comparison.Is, null);
            deleteQuery.Execute();
        }
    }
}
